package com.wizo.csb.workload;

import java.util.Properties;
import java.io.*;

import com.wizo.csb.*;
import com.wizo.csb.generator.*;
import com.wizo.csb.records.Records;
import com.wizo.csb.iostream.*;

/**
 * The core benchmark scenario. Represents a set of clients doing simple I/O operations. The relative
 * proportion of different kinds of operations, and other properties of the workload, are controlled
 * by parameters specified at runtime.
 * @author ywu70
 *
 */
public class CoreWorkload extends Workload{
	public static final String CREATE_START_PROPERTY = "writeStart";
	
	public static final String CREATE_START_PROPERTY_DEFAULT = "0";
	
	int _containerCount;

	int _createObjectCount;
	/**
	 * The name of the property for the size of an object in bytes.
	 */
	public static final String OBJECT_SIZE_PROPERTY = "objectSize";
	
	/**
	 * The default size of an object in bytes.
	 */
	public static final String OBJECT_SIZE_PROPERTY_DEFAULT = "4K";
	
	long _objectSize;
	
	/**
	 * Granularity for time series; measurements will be averaged when the tool run
	 * every other time series. Units are ms
	 */
	public static final String GRANULARITY = "timeSeries.granularity";
	/**
	 * The default value of granularity for time series
	 */
	public static final String GRANULARITY_DEFAULT = "3000";
	
	/**
	 * The name of the property for the proportion of operations that are reads.
	 */
	public static final String READ_PROPORTION_PROPERTY = "readProportion";
	
	/**
	 * The default proportion of operations that are reads.
	 */
	public static final String READ_PROPORTION_PROPERTY_DEFAULT = "0";
	
	/**
	 * The name of the property for the proportion of operations that are writes.
	 */
	public static final String WRITE_PROPORTION_PROPERTY = "writeProportion";
	
	/**
	 * The default proportion of operations that are writes.
	 */
	public static final String WRITE_PROPORTION_PROPERTY_DEFAULT = "0";
	
	/**
	 * The name of the property for the proportion of operations that are deletes.
	 */
	public static final String DELETE_PROPORTION_PROPERTY = "deleteProportion";
	
	/**
	 * The default proportion of operations that are deletes.
	 */
	public static final String DELETE_PROPORTION_PROPERTY_DEFAULT = "0";
	
	/**
	 * The name of the property for the proportion of operations that are copies.
	 */
	public static final String COPY_PROPORTION_PROPERTY = "copyProportion";
	
	/**
	 * The default proportion of operations that are copies.
	 */
	public static final String COPY_PROPORTION_PROPERTY_DEFAULT = "0";
	
	/**
	 * A generator used for generating the experimental object name,
	 * e.g.: like fileX, the X is an Integer that is generated by this generator. 
	 */
	CounterGenerator _objectNameSequence;
	
	/**
	 * A generator used for generating the experimental object name when doing the writing transaction.
	 */
	CounterGenerator _transactionWriteObjectNameSequence;
	
	/**
	 * A generator used for generating the experiment object name when doing the reading/copying
	 *  transaction.
	 */
	BoundIntegerGenerator _createObjectNameChooser;
	
	/**
	 * A generator used for generating the type of operation need to do.
	 */
	DiscreteGenerator _operationChooser;
	
	/**
	 * A generator used for generating the random bucket name.
	 */
	ContainerNameGenerator _containerNameChooser;
	
	int _outputSize;
	
	/**
	 * Initialize the scenario.
	 * Called once, in the main client thread, before any operations are started.
	 */
	public void init(Properties p) throws WorkloadException{
		_objectSize = Assist.getObjectSizeFromString(p.getProperty(OBJECT_SIZE_PROPERTY, 
				OBJECT_SIZE_PROPERTY_DEFAULT));

		_createObjectCount = Integer.parseInt(p.getProperty(Client.CREATE_OBJECT_COUNT_PROPERTY));
		int createStart = Integer.parseInt(p.getProperty(CREATE_START_PROPERTY, 
				CREATE_START_PROPERTY_DEFAULT));
		_objectNameSequence = new CounterGenerator(createStart);
		
		_containerCount = Integer.parseInt(p.getProperty("containerCount"));
		_containerNameChooser = new ContainerNameGenerator(_containerCount);
		for(int i=0; i<_containerCount; i++){
			_containerNameChooser.addValue(p.getProperty("container"+i));
		}

		double readProportion = Double.parseDouble(p.getProperty(READ_PROPORTION_PROPERTY, 
				READ_PROPORTION_PROPERTY_DEFAULT));
		double writeProportion = Double.parseDouble(p.getProperty(WRITE_PROPORTION_PROPERTY, 
				WRITE_PROPORTION_PROPERTY_DEFAULT));
		double deleteProportion = Double.parseDouble(p.getProperty(DELETE_PROPORTION_PROPERTY, 
				DELETE_PROPORTION_PROPERTY_DEFAULT));
		double copyProportion = Double.parseDouble(p.getProperty(COPY_PROPORTION_PROPERTY, 
				COPY_PROPORTION_PROPERTY_DEFAULT));
		
		_operationChooser = new DiscreteGenerator();
		if(readProportion>0)
			_operationChooser.addValue(readProportion, "READ");
		if(writeProportion>0)
			_operationChooser.addValue(writeProportion, "WRITE");
		if(deleteProportion>0)
			_operationChooser.addValue(deleteProportion, "DELETE");
		if(copyProportion>0)
			_operationChooser.addValue(copyProportion, "COPY");
		
		_transactionWriteObjectNameSequence = new CounterGenerator(_createObjectCount);
		
		_createObjectNameChooser = new BoundIntegerGenerator(0, _createObjectCount-1);
		
		_outputSize = (int)Assist.getObjectSizeFromString(p.getProperty("outputSize", "8K"));
	}
	
	
	
	/**
	 * Do one create operation. 
	 * Because it will be called concurrently from multiple client threads, 
	 * this function must be thread safe. However, avoid synchronized, or the threads will 
	 * block waiting for each other, and it will be difficult to reach the target throughput. 
	 * Ideally, this function would have no side effects other than storage operations.
	 */
	public boolean doCreate(Records measurements, Storage storage){
		int objectIndex = _objectNameSequence.nextInteger();
		String objectName = "folder_"+Assist.getObjectSizeFromLong(_objectSize)+"/object_"+objectIndex;
		int result = 0;
		InputStream inputStream = null;
		
		for(int i=0; i<_containerCount; i++){
			String container = _containerNameChooser.containerAt(i);
			inputStream = new CyclicalByteArrayInputStream(_objectSize);
			long start = System.currentTimeMillis();
			result = storage.upload(container, objectName, inputStream, _objectSize, "text/plain");
			long end = System.currentTimeMillis();
			
			if(result!=0)
				return false;
			measurements.record("WRITE", end-start);
		}
		
		try {
			inputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return true;
	}
	
	/**
	 * Do one transaction operation. 
	 * Because it will be called concurrently from multiple client threads, 
	 * this function must be thread safe. However, avoid synchronized, or the threads 
	 * will block waiting for each other, and it will be difficult to reach the target throughput. 
	 * Ideally, this function would have no side effects other than storage operations.
	 */
	public boolean doTransaction(Records measurements, Storage storage, String container){
		if(container==null)
			container = _containerNameChooser.nextString();
		
		String operation = _operationChooser.nextString();
		
		if(operation.compareTo("READ")==0)
			doTransactionRead(measurements, storage, container);
		else if(operation.compareTo("WRITE")==0)
			doTransactionWrite(measurements, storage, container);
		else if(operation.compareTo("DELETE")==0)
			doTransactionDelete(measurements, storage, container);
		else if(operation.compareTo("COPY")==0){
			String bucketDes = _containerNameChooser.nextString();
			doTransactionCopy(measurements, storage, container, bucketDes);
		}
		return true;
	}
	
	public void doTransactionRead(Records measurements, Storage storage, String container){
		int objectIndex = _createObjectNameChooser.nextInteger();
		String objectName = "folder_"+Assist.getObjectSizeFromLong(_objectSize)+"/object_"+objectIndex;
		InputStream inputStream = null;
		byte[] temp = new byte[_outputSize];
		
		long start=0, end=0;
		
		start = System.currentTimeMillis();
		inputStream = storage.download(container, objectName);
		
		if(inputStream!=null){
			try {
				while(inputStream.read(temp)!=-1){
					//Do nothing
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		end = System.currentTimeMillis();
		
		measurements.record("READ", (int)(end-start));
		
		if(inputStream!=null){
			try{
				inputStream.close();
			}catch(IOException e){
				e.printStackTrace();
			}
		}
	}
	
	public void doTransactionWrite(Records measurements, Storage storage, String container){
		int objectIndex = _transactionWriteObjectNameSequence.nextInteger();
		String objectName = "folder_"+Assist.getObjectSizeFromLong(_objectSize)+"/object_"+objectIndex;
		InputStream inputStream = new CyclicalByteArrayInputStream(_objectSize);
		
		long start=0, end=0;
		
		start = System.currentTimeMillis();
		storage.upload(container, objectName, inputStream, _objectSize, "text/plain");
		end = System.currentTimeMillis();
		
		measurements.record("WRITE", (int)(end-start));
		
//		storage.delete(bucket, objectName);
		
		if(inputStream!=null){
			try{
				inputStream.close();
			}catch(IOException e){
				e.printStackTrace();
			}
		}
	}
	
	public void doTransactionDelete(Records measurements, Storage storage, String container){
		int objectIndex = _createObjectNameChooser.nextInteger();
		String objectName = "folder_"+Assist.getObjectSizeFromLong(_objectSize)+"/object_"+objectIndex;
		
		long start = System.currentTimeMillis();
		storage.delete(container, objectName);
		long end = System.currentTimeMillis();
		
		measurements.record("DELETE", (int)(end-start));
		
		InputStream inputStream = new CyclicalByteArrayInputStream(_objectSize);
		storage.upload(container, objectName, inputStream, _objectSize, "text/plain");
		
		if(inputStream!=null){
			try{
				inputStream.close();
			}catch(IOException e){
				e.printStackTrace();
			}
		}
	}
	
	public void doTransactionCopy(Records measurements, Storage storage, 
			String containerSource, String containerDestination){
		int objectIndex = _createObjectNameChooser.nextInteger();
		String objectName = "folder_"+Assist.getObjectSizeFromLong(_objectSize)+"/object_"+objectIndex;
		long start=0, end=0;
		
		start = System.currentTimeMillis();
		storage.copy(containerSource, objectName, containerDestination, 
				objectName+".copyFrom_"+containerSource, false);
		end = System.currentTimeMillis();
		
		measurements.record("COPY", (int)(end-start));
		
	}

}
